Code coverage report for src/rateLimiter.js

Statements: 86% (43 / 50)      Branches: 82.86% (29 / 35)      Functions: 85.71% (6 / 7)      Lines: 86% (43 / 50)      Ignored: none     

All files » src/ » rateLimiter.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129                8 8 8 8 8 8     1                           1 8 6   8 6   8 6                                     1 15 15 5 5     15 15   15 1 14 1   13 13   13 13     1               42       15 15 2   15 2   15       2 2                             2       2       2    
/*
 * RateLimiter - an object that encapsulates the logic for counting items sent to Rollbar
 *
 * @param options - the same options that are accepted by configureGlobal offered as a convenience
 */
function RateLimiter(options) {
  this.startTime = (new Date()).getTime();
  this.counter = 0;
  this.perMinCounter = 0;
  this.platform = null;
  this.platformOptions = {};
  this.configureGlobal(options);
}
 
RateLimiter.globalSettings = {
  startTime: (new Date()).getTime(),
  maxItems: undefined,
  itemsPerMinute: undefined
};
 
/*
 * configureGlobal - set the global rate limiter options
 *
 * @param options - Only the following values are recognized:
 *    startTime: a timestamp of the form returned by (new Date()).getTime()
 *    maxItems: the maximum items
 *    itemsPerMinute: the max number of items to send in a given minute
 */
RateLimiter.prototype.configureGlobal = function(options) {
  if (options.startTime !== undefined) {
    RateLimiter.globalSettings.startTime = options.startTime;
  }
  if (options.maxItems !== undefined) {
    RateLimiter.globalSettings.maxItems = options.maxItems;
  }
  if (options.itemsPerMinute !== undefined) {
    RateLimiter.globalSettings.itemsPerMinute = options.itemsPerMinute;
  }
};
 
/*
 * shouldSend - determine if we should send a given item based on rate limit settings
 *
 * @param item - the item we are about to send
 * @returns An object with the following structure:
 *  error: (Error|null)
 *  shouldSend: bool
 *  payload: (Object|null)
 *  If shouldSend is false, the item passed as a parameter should not be sent to Rollbar, and
 *  exactly one of error or payload will be non-null. If error is non-null, the returned Error will
 *  describe the situation, but it means that we were already over a rate limit (either globally or
 *  per minute) when this item was checked. If error is null, and therefore payload is non-null, it
 *  means this item put us over the global rate limit and the payload should be sent to Rollbar in
 *  place of the passed in item.
 */
RateLimiter.prototype.shouldSend = function(item, now) {
  now = now || (new Date()).getTime();
  if (now - this.startTime >= 60000) {
    this.startTime = now;
    this.perMinCounter = 0;
  }
 
  var globalRateLimit = RateLimiter.globalSettings.maxItems;
  var globalRateLimitPerMin = RateLimiter.globalSettings.itemsPerMinute;
 
  if (checkRate(item, globalRateLimit, this.counter)) {
    return shouldSendValue(this.platform, this.platformOptions, globalRateLimit + ' max items reached', false);
  } else if (checkRate(item, globalRateLimitPerMin, this.perMinCounter)) {
    return shouldSendValue(this.platform, this.platformOptions, globalRateLimitPerMin + ' items per minute reached', false);
  }
  this.counter++;
  this.perMinCounter++;
 
  var shouldSend = !checkRate(item, globalRateLimit, this.counter);
  return shouldSendValue(this.platform, this.platformOptions, null, shouldSend, globalRateLimit);
};
 
RateLimiter.prototype.setPlatformOptions = function(platform, options) {
  this.platform = platform;
  this.platformOptions = options;
};

/* Helpers */
 
function checkRate(item, limit, counter) {
  return !item.ignoreRateLimit && limit >= 1 && counter >= limit;
}
 
function shouldSendValue(platform, options, error, shouldSend, globalRateLimit) {
  var payload = null;
  if (error) {
    error = new Error(error);
  }
  if (!error && !shouldSend) {
    payload = rateLimitPayload(platform, options, globalRateLimit);
  }
  return {error: error, shouldSend: shouldSend, payload: payload};
}
 
function rateLimitPayload(platform, options, globalRateLimit) {
  var environment = options.environment || (options.payload && options.payload.environment);
  var item = {
    body: {
      message: {
        body: 'maxItems has been hit. Ignoring errors until reset.',
        extra: {
          maxItems: globalRateLimit
        }
      }
    },
    language: 'javascript',
    environment: environment,
    notifier: {
      version: (options.notifier && options.notifier.version) || options.version
    }
  };
  if (platform === 'browser') {
    item.platform = 'browser';
  I  item.framework = 'browser-js';
    item.notifier.name = 'rollbar-browser-js';
  } else if (platform === 'server') {
    item.framework = options.framework || 'node-js';
    item.Inotifier.name = options.notifier.name;
  }
  return item;
}
 
module.exports = RateLimiter;